package com.wlz;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 *  I/O多路复用器
 *     什么是Selector：
 *        解释:在一个养鸡场，有这么一个人，每天的工作就是不停检查几个特殊的鸡笼，如果有鸡进来，有鸡出去，有鸡生蛋，有鸡生病等等，
 *          就把相应的情况记录下来，如果鸡场的负责人想知道情况，只需要询问那个人即可。
 *     在这里，这个人就相当Selector，每个鸡笼相当于一个SocketChannel，每个线程通过一个Selector可以管理多个SocketChannel。
 *
 *     为了实现Selector管理多个SocketChannel，必须将具体的SocketChannel对象注册到Selector，
 *     并声明需要监听的事件（这样Selector才知道需要记录什么数据），
 *     一共有4种事件：
 *          connect：客户端连接服务端事件，对应值为SelectionKey.OP_CONNECT(8)
 *          accept：服务端接收客户端连接事件，对应值为SelectionKey.OP_ACCEPT(16)
 *          read：读事件，对应值为SelectionKey.OP_READ(1)
 *          write：写事件，对应值为SelectionKey.OP_WRITE(4)
 *
 *      理解:每次请求到达服务器，都是从connect开始，connect成功后，服务端开始准备accept，准备就绪，开始读数据，并处理，最后写回数据返回。
 *          所以，当SocketChannel有对应的事件发生时，Selector都可以观察到，并进行相应的处理。
 *
 */
public class NioServerTest {

    private static ServerSocketChannel serverSocketChannel;

    private static Selector selector;

    public static void main(String[] args) throws IOException {

        try {
            serverSocketChannel = ServerSocketChannel.open();
            selector = Selector.open();
            serverSocketChannel.bind(new InetSocketAddress(8080));
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        } catch (IOException e) {
            e.printStackTrace();
        }

        while (true) {
            // nio 非阻塞io
            int select = selector.select();// 线程阻塞
            if (select <= 0) {
                System.out.println("没有连接发来数据，跳出此次循环---");
                continue; // 如果没有连接发来数据，跳过此次循环
            }

            // 事件列表
            Set<SelectionKey> selectionKeys = selector.selectedKeys(); // Epoll()
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            SelectionKey selectionKey = null;
            while (iterator.hasNext()) {
                selectionKey = iterator.next();
                //分发事件
                handle(selectionKey);
                iterator.remove();
            }

        }

    }

    /**
     *  读
     * @param key
     * @throws IOException
     */
    private static void handle(SelectionKey key) throws IOException {
        if (key.isValid()) {
            if (key.isAcceptable()) { // 新链接建立，注册
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                // 将选择器注册到客户端信道
                // 并指定该信道key值的属性为OP_READ,
                // 同时为该信道指定关联的附件
                sc.register(selector,SelectionKey.OP_READ);
            }
            if (key.isReadable()) { // 读事件处理
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024); //堆缓冲区

                int read = sc.read(byteBuffer); // recvFrom()
                if (read > 0) {
                    byteBuffer.flip();// 读写模式反转
                    byte[] requerst = new byte[byteBuffer.remaining()];
                    byteBuffer.get(requerst);
                    System.out.println("input is:"+ new String(requerst));
                    write(sc,"ok");
                }
            }
        }

    }

    /**
     * 写
     * @param sc
     * @param response
     * @throws IOException
     */
    private static void write(SocketChannel sc,String response) throws IOException {
        if (response != null && response.length() > 0) {
            byte[] bytes = response.getBytes();
            ByteBuffer buffer = ByteBuffer.allocateDirect(1024); // 直接缓冲区(直接内存) ->内核态的内存
//            (DirectByteBuffer)buffer.cleaner();  手动释放直接内存
            buffer.put(bytes);
            buffer.flip();
            sc.write(buffer);
        }
    }
}
